#include "..\inc\Game.h"
using std::pair;
using std::vector;

/**
 * @brief keep pushing number to one direction
 * @param [in] key_direction
 * @param [out] data of the matrix
 * @return move success or not
 */
bool Game::number_move(KeyDirection key_direction, Data &data)
{
    bool move_success = false;
    switch (key_direction)
    {
        case KeyDirection::UP:
        {
            for (int column = 0; column < 4; column++)
            {
                int pointer_goal    = 0;
                int pointer_current = 1;
                // found first 0, and setting it as the moving target position
                while (pointer_goal <= 2 && pointer_current <= 3)
                {
                    if (data.get_number(pointer_goal, column) != 0)
                    {
                        // if current position is not 0, keep searching next 0 downward
                        pointer_goal++;
                        pointer_current = pointer_goal + 1;
                    }
                    else  // current position is 0, searching moving position
                    {
                        if (data.get_number(pointer_current, column) != 0)
                        {
                            // able to move
                            data.set_number(pointer_goal, column, data.get_number(pointer_current, column));
                            data.set_number(pointer_current, column, 0);
                            pointer_goal++;
                            pointer_current = pointer_goal + 1;
                            move_success    = true;
                        }
                        else  // keep searching downward if moving position is 0
                        {
                            pointer_current++;
                        }
                    }
                }
            }
            break;
        }
        case KeyDirection::DOWN:
        {
            for (int column = 0; column < 4; column++)
            {
                int pointer_goal    = 3;
                int pointer_current = 2;
                // found first 0, and setting it as the moving target position
                while (pointer_goal >= 1 && pointer_current >= 0)
                {
                    if (data.get_number(pointer_goal, column) != 0)
                    {
                        // if current position is not 0, keep searching next 0 upward
                        pointer_goal--;
                        pointer_current = pointer_goal - 1;
                    }
                    else  // current position is 0, searching moving position
                    {
                        if (data.get_number(pointer_current, column) != 0)
                        {
                            // able to move
                            data.set_number(pointer_goal, column, data.get_number(pointer_current, column));
                            data.set_number(pointer_current, column, 0);
                            pointer_goal--;
                            pointer_current = pointer_goal - 1;
                            move_success    = true;
                        }
                        else  // keep searching upward if moving position is 0
                        {
                            pointer_current--;
                        }
                    }
                }
            }
            break;
        }
        case KeyDirection::LEFT:
        {
            for (int row = 0; row < 4; row++)
            {
                int pointer_goal    = 0;
                int pointer_current = 1;
                // found first 0, and setting it as the moving target position
                while (pointer_goal <= 2 && pointer_current <= 3)
                {
                    if (data.get_number(row, pointer_goal) != 0)
                    {
                        // if current position is not 0, keep searching next 0 backward
                        pointer_goal++;
                        pointer_current = pointer_goal + 1;
                    }
                    else  // current position is 0, searching moving position
                    {
                        if (data.get_number(row, pointer_current) != 0)
                        {
                            // able to move
                            data.set_number(row, pointer_goal, data.get_number(row, pointer_current));
                            data.set_number(row, pointer_current, 0);
                            pointer_goal++;
                            pointer_current = pointer_goal + 1;
                            move_success    = true;
                        }
                        else  // keep searching backward if moving position is 0
                        {
                            pointer_current++;
                        }
                    }
                }
            }
            break;
        }
        case KeyDirection::RIGHT:
        {
            for (int row = 0; row < 4; row++)
            {
                int pointer_goal    = 3;
                int pointer_current = 2;
                // found first 0, and setting it as the moving target position
                while (pointer_goal >= 1 && pointer_current >= 0)
                {
                    if (data.get_number(row, pointer_goal) != 0)
                    {
                        // if current position is not 0, keep searching next 0 forward
                        pointer_goal--;
                        pointer_current = pointer_goal - 1;
                    }
                    else  // current position is 0, searching moving position
                    {
                        if (data.get_number(row, pointer_current) != 0)
                        {
                            // able to move
                            data.set_number(row, pointer_goal, data.get_number(row, pointer_current));
                            data.set_number(row, pointer_current, 0);
                            pointer_goal--;
                            pointer_current = pointer_goal - 1;
                            move_success    = true;
                        }
                        else  // keep searching forward if moving position is 0
                        {
                            pointer_current--;
                        }
                    }
                }
            }
            break;
        }
        default:
            break;
    }
    return move_success;
}

/**
 * @brief add number to one direction
 * @param [in] key_direction
 * @param [out] data
 * @return number add success or not
 */
bool Game::number_add(KeyDirection key_direction, Data &data)
{
    bool add_success = false;
    switch (key_direction)
    {
        case KeyDirection::UP:
        {
            // Process column by column
            for (int column = 0; column < 4; column++)
            {
                // Find adjacent identical numbers
                int pointer = 0;
                while (pointer <= 2)
                {
                    // The current position is the same as the number below
                    if (data.get_number(pointer, column) == data.get_number(pointer + 1, column) && data.get_number(pointer, column) != 0)
                    {
                        // double itself
                        int temp = data.get_number(pointer, column);
                        data.set_number(pointer, column, temp * 2);
                        // The numbers below move up one by one
                        for (int i = pointer + 1; i < 3; i++)
                        {
                            data.set_number(i, column, data.get_number(i + 1, column));
                        }
                        // Set the last number to zero
                        data.set_number(3, column, 0);
                        add_success = true;
                        pointer++;
                    }
                    else
                    {
                        pointer++;
                    }
                }
            }
            break;
        }
        case KeyDirection::DOWN:
        {
            // Process column by column
            for (int column = 0; column < 4; column++)
            {
                // Find adjacent identical numbers
                int pointer = 3;
                while (pointer >= 1)
                {
                    // The current position is the same as the number above
                    if (data.get_number(pointer, column) == data.get_number(pointer - 1, column) && data.get_number(pointer, column) != 0)
                    {
                        // double itself
                        int temp = data.get_number(pointer, column);
                        data.set_number(pointer, column, temp * 2);
                        // // The numbers above move down one by one
                        for (int i = pointer - 1; i > 0; i--)
                        {
                            data.set_number(i, column, data.get_number(i - 1, column));
                        }
                        // Set the first number to zero
                        data.set_number(0, column, 0);
                        add_success = true;
                        pointer--;
                    }
                    else
                    {
                        pointer--;
                    }
                }
            }
            break;
        }
        case KeyDirection::LEFT:
        {
            // Process row by row
            for (int row = 0; row < 4; row++)
            {
                // Find adjacent identical numbers
                int pointer = 0;
                while (pointer <= 2)
                {
                    // The current position is the same as the following numbers
                    if (data.get_number(row, pointer) == data.get_number(row, pointer + 1) && data.get_number(row, pointer) != 0)
                    {
                        // double itself
                        int temp = data.get_number(row, pointer);
                        data.set_number(row, pointer, temp * 2);
                        // Move the following numbers forward one by one
                        for (int i = pointer + 1; i < 3; i++)
                        {
                            data.set_number(row, i, data.get_number(row, i + 1));
                        }
                        // Set the last number to zero
                        data.set_number(row, 3, 0);
                        add_success = true;
                        pointer++;
                    }
                    else
                    {
                        pointer++;
                    }
                }
            }
            break;
        }
        case KeyDirection::RIGHT:
        {
            // Process row by row
            for (int row = 0; row < 4; row++)
            {
                // Find adjacent identical numbers
                int pointer = 3;
                while (pointer >= 1)
                {
                    // The current position is the same as the previous number
                    if (data.get_number(row, pointer) == data.get_number(row, pointer - 1) && data.get_number(row, pointer) != 0)
                    {
                        // double itself
                        int temp = data.get_number(row, pointer);
                        data.set_number(row, pointer, temp * 2);
                        // Move the previous numbers one by one back
                        for (int i = pointer - 1; i > 0; i--)
                        {
                            data.set_number(row, i, data.get_number(row, i - 1));
                        }
                        // Set the first number to zero
                        data.set_number(row, 0, 0);
                        add_success = true;
                        pointer--;
                    }
                    else
                    {
                        pointer--;
                    }
                }
            }
            break;
        }
        default:
            break;
    }
    return add_success;
}

/**
 * @brief add random number to empty place
 * @param [out] data
 * @param [out] new_row
 * @param [out] new_column
 * @return add random number success or not
 */
bool Game::add_random_num(Data &data, int &new_row, int &new_column)
{
    // record every empty position
    vector<pair<int, int>> empty_place;
    for (int row = 0; row < 4; row++)
    {
        for (int column = 0; column < 4; column++)
        {
            if (data.is_empty(row, column))
            {
                empty_place.emplace_back(row, column);
            }
        }
    }
    if (empty_place.empty())
    {
        return false;
    }
    // generate random number
    std::random_device rd;
    std::default_random_engine e(rd() * time(nullptr));
    std::uniform_int_distribution<int> distribution_1(0, (int) empty_place.size() - 1);
    int random_place = distribution_1(e);

    std::uniform_int_distribution<int> distribution_2(0, 9);
    int random_num = distribution_2(e);

    // 90% for adding "2"，10% for adding "4"
    int add_num;
    if (random_num <= 8)
    {
        add_num = 2;
    }
    else
    {
        add_num = 4;
    }

    // adding number
    int row    = empty_place.at(random_place).first;
    int column = empty_place.at(random_place).second;
    data.set_number(row, column, add_num);

    // return adding position
    new_row    = row;
    new_column = column;

    return true;
}

/**
 * @brief calculate score
 * @param [in]data
 * @return score
 */
int Game::calculate_score(const Data &data)
{
    int score = 0;
    for (int row = 0; row < 4; row++)
    {
        for (int column = 0; column < 4; column++)
        {
            score += data.get_number(row, column);
        }
    }
    return score;
}

/**
 * @brief check if game is over(all position is full and no two adjacent numbers are the same)
 * @param [in]data
 * @return game is over or not
 */
bool Game::fail(const Data &data)
{
    for (int row = 0; row < 4; row++)
    {
        for (int column = 0; column < 4; column++)
        {
            // not fail at least one empty position
            if (data.is_empty(row, column))
            {
                return false;
            }
            // check same number downward
            if (row < 3)
            {
                if (data.get_number(row, column) == data.get_number(row + 1, column))
                {
                    return false;
                }
            }
            // check same number rightward
            if (column < 3)
            {
                if (data.get_number(row, column) == data.get_number(row, column + 1))
                {
                    return false;
                }
            }
        }
    }
    return true;
}

/**
 * @brief check if any number reach 2048
 * @param data
 * @return success or not
 */
bool Game::success(const Data &data, const int &score)
{
    for (int row = 0; row < 4; row++)
    {
        for (int column = 0; column < 4; column++)
        {
            if (data.get_number(row, column) >= score)
                return true;
        }
    }
    return false;
}
